#include "Visualisation.h"
#include "Sprite.h"
#include "Animation.h"
#include <assert.h>
#include "Rectangle.h"

CVisualisation* CVisualisation::instance=NULL;

CVisualisation::CVisualisation(void):screen(NULL), screenHeight(0), screenWidth(0), beginTimeBlending(-1), 
									 endTimeBlending(-1), element(NULL)
{}

CVisualisation::~CVisualisation(void)
{
	//destruction of the texture
	for (vector<CSprite*>::iterator p=vectorSprite.begin(); p<vectorSprite.end(); p++)
	{
		delete (*p);
	}

	//destruction of the animation
	for (vector<CAnimation*>::iterator p=vectorAnimation.begin(); p<vectorAnimation.end(); p++)
	{
		delete (*p);
	}
	this->screen=NULL;
}

CVisualisation& CVisualisation::getInstance(void)
{
	if(instance==NULL)
	{
		instance=new CVisualisation();
	}
	return *instance;
}

/*function that close the Visualisation component and destroy every graphic element*/
void CVisualisation::Close(void)
{
	delete CVisualisation::instance;
	CVisualisation::instance=NULL;
}
/*function that create a sprite and return true if the creation succeds or false if it fails, 
index contains the index of the sprite.*/
bool CVisualisation::createSprite(int* index, TiXmlElement* const element)
{
	if(!element)return false;
	//we get the data from the xml file
	int frameWidth;
	int frameHeight;
	int numberOfFramePerRow;
	int numberOfFrame;
	string adressTexture=element->GetText();	
	element->QueryIntAttribute("frameWidth",&frameWidth);
	element->QueryIntAttribute("frameHeight",&frameHeight);
	element->QueryIntAttribute("numberOfFramePerRow",&numberOfFramePerRow);
	element->QueryIntAttribute("numberOfFrame", &numberOfFrame);

	//creation of the sprite
	CSprite* newSprite = new CSprite();
	if(!newSprite->initialise(adressTexture))
	{
		delete newSprite;
		return false;
	}
	newSprite->setWidthFrame(frameWidth);
	newSprite->setHeightFrame(frameHeight);
	newSprite->setNbOfFramePerRow(numberOfFramePerRow);
	newSprite->setNbOfFrame(numberOfFrame);

	(*index)=(int)vectorSprite.size();
	vectorSprite.push_back(newSprite);
	return true;
}

/*function that creates an animation and return true if the creation succeds or false if it fails, 
index contains the index of the animation.*/
bool CVisualisation::createAnimation(int indexSprite, int* indexAnimation, int firstFrame, int lastFrame, 
									 int time)
{
	CAnimation* newAnimation = new CAnimation(indexSprite, firstFrame,lastFrame,time);
	(*indexAnimation)=(int)this->vectorAnimation.size();
	this->vectorAnimation.push_back(newAnimation);
	return true;
}

/*function that creates an font and return true if the creation succeds or false if it fails.
The index of the police is in the vectorIndexFont vector.*/
bool CVisualisation::createFont(TiXmlElement* element)
{
	
	if(!element)return false;
	//we get the data from the xml file
	int frameWidth;
	int frameHeight;
	int numberOfFramePerRow;
	int numberOfFrame;
	string adressTexture=element->GetText();	
	element->QueryIntAttribute("frameWidth",&frameWidth);
	element->QueryIntAttribute("frameHeight",&frameHeight);
	element->QueryIntAttribute("numberOfFramePerRow",&numberOfFramePerRow);
	element->QueryIntAttribute("numberOfFrame", &numberOfFrame);
	
	//creation of the police
	CSprite* newSprite = new CSprite();
	if(!newSprite->initialise(adressTexture))
	{
		delete newSprite;
		return false;
	}
	newSprite->setWidthFrame(frameWidth);
	newSprite->setHeightFrame(frameHeight);
	newSprite->setNbOfFramePerRow(numberOfFramePerRow);
	newSprite->setNbOfFrame(numberOfFrame);

	vectorIndexFont.push_back((int)vectorSprite.size());
	vectorSprite.push_back(newSprite);
	
	return true;
}

/*function that display a sprite, that is to say the entiere texture.*/
bool CVisualisation::displaySprite(int positionX, int positionY, int index, bool transparency)
{
	assert(index>=0);
	assert(index<(int)vectorSprite.size());
	assert(this->screenWidth>0);
	assert(this->screenHeight>0);
	assert(vectorSprite[index]->getSpriteWidth()>0);
	assert(vectorSprite[index]->getSpriteHeight()>0);

	//if the texture is entierly in the screen
	if(positionX>=0 && positionX<=this->screenWidth-vectorSprite[index]->getSpriteWidth()
		&& positionY>=0 && positionY<=this->screenHeight-vectorSprite[index]->getSpriteHeight())
	{
		return vectorSprite[index]->blitSprite( this->screen, positionX,  positionY,  this->screenWidth, 
							this->screenHeight, transparency);
	}
	//if the texture is entierly out of the screen
	else if(positionX>this->screenWidth || positionX<-vectorSprite[index]->getSpriteWidth() 
		|| positionY>this->screenHeight || positionY<-vectorSprite[index]->getSpriteHeight())
	{
		return true;
	}
	//if we gotta clip
	else
	{
		//I create my 2 rectangles
		CRectangle source(0,vectorSprite[index]->getSpriteWidth(),0,vectorSprite[index]->getSpriteHeight());
		CRectangle destination(0,this->screenWidth,0,this->screenHeight);

		//convert source rectangle into destination space

		source.setLeft(source.getLeft()+positionX);
		source.setRight(source.getRight()+positionX);
		source.setTop(source.getTop()+positionY);
		source.setBottom(source.getBottom()+positionY);

		//clip against the destination rectangle

		if(source.getLeft()<destination.getLeft())
		{
			source.setLeft(destination.getLeft());
		}

		if(source.getRight()>destination.getRight())
		{
			source.setRight(destination.getRight());
		}

		if(source.getTop()<destination.getTop())
		{
			source.setTop(destination.getTop());
		}

		if(source.getBottom()>destination.getBottom())
		{
			source.setBottom(destination.getBottom());
		}

		//convert it back into source space

		source.setLeft(source.getLeft()-positionX);
		source.setRight(source.getRight()-positionX);
		source.setTop(source.getTop()-positionY);
		source.setBottom(source.getBottom()-positionY);

		//clamp negative position values to 0

		if(positionX<0)
		{
			positionX=0;
		}
		if(positionY<0)
		{
			positionY=0;
		}
		//display the texture with the blit function that handle the clipping
		return vectorSprite[index]->blitTextureClipped( screen, positionX, positionY,screenWidth, 
									screenHeight, source, transparency);
	}
}

/*function that display a frame, that is to say a part of a texture. indexFrame begin to 1. The size of the
frame are constant and correspond to the value of frameWidth an frameHeight given during the creation of 
the sprite*/
bool CVisualisation::displayFrame(int indexSprite, int positionX, int positionY, int indexFrame, 
									  bool transparency)
{
		assert(indexSprite>=0);
		assert(indexSprite<(int)vectorSprite.size());

		int NbOfFramePerRow= this->vectorSprite[indexSprite]->getNbOfFramePerRow();

		int left=(indexFrame-1)%(NbOfFramePerRow)*this->vectorSprite[indexSprite]->getWidthFrame();
		int right = left+this->vectorSprite[indexSprite]->getWidthFrame();
		int top=this->vectorSprite[indexSprite]->getHeightFrame()*((indexFrame-1)/NbOfFramePerRow);
		int bottom = top +this->vectorSprite[indexSprite]->getHeightFrame();

		CRectangle frame(left,right,top,bottom);
		
		//if the texture is entierly in the screen
		if(positionX>=0 && positionX<=this->screenWidth-vectorSprite[indexSprite]->getWidthFrame()
			&& positionY>=0 && positionY<=this->screenHeight-vectorSprite[indexSprite]->getHeightFrame())
		{
			return this->vectorSprite[indexSprite]->blitTextureClipped(this->screen,positionX, positionY,
				this->screenWidth, this->screenHeight,frame,transparency);	
		}
		//if the texture is entierly out of the screen
		else if(positionX>this->screenWidth || positionX<-vectorSprite[indexSprite]->getWidthFrame() 
			|| positionY>this->screenHeight || positionY<-vectorSprite[indexSprite]->getHeightFrame())
		{
			return true;
		}
		//if we gotta clip
		else
		{
		//I create my rectangle
			frame.setTop(0);
			frame.setLeft(0);
			frame.setRight(this->vectorSprite[indexSprite]->getWidthFrame());
			frame.setBottom(this->vectorSprite[indexSprite]->getHeightFrame());

			CRectangle destination(0,this->screenWidth,0,this->screenHeight);

			//convert source rectangle into destination space

			frame.setLeft(frame.getLeft()+positionX);
			frame.setRight(frame.getRight()+positionX);
			frame.setTop(frame.getTop()+positionY);
			frame.setBottom(frame.getBottom()+positionY);

			//clip against the destination rectangle

			if(frame.getLeft()<destination.getLeft())
			{
				frame.setLeft(destination.getLeft());
			}

			if(frame.getRight()>destination.getRight())
			{
				frame.setRight(destination.getRight());
			}

			if(frame.getTop()<destination.getTop())
			{
				frame.setTop(destination.getTop());
			}

			if(frame.getBottom()>destination.getBottom())
			{
				frame.setBottom(destination.getBottom());
			}

			//convert it back into source space
			
			frame.setLeft(frame.getLeft()-positionX);
			frame.setRight(frame.getRight()-positionX);
			frame.setTop(frame.getTop()-positionY);
			frame.setBottom(frame.getBottom()-positionY);

			//.............................................//
			//we put the rectangle on the good frame
			frame.setLeft(frame.getLeft()+((indexFrame-1)%NbOfFramePerRow)*this->vectorSprite[indexSprite]->getWidthFrame());
			frame.setRight(frame.getRight()+((indexFrame-1)%NbOfFramePerRow)*this->vectorSprite[indexSprite]->getWidthFrame());
			
			frame.setTop(frame.getTop()+((indexFrame-1)/NbOfFramePerRow)*this->vectorSprite[indexSprite]->getHeightFrame());
			frame.setBottom(frame.getBottom()+((indexFrame-1)/NbOfFramePerRow)*this->vectorSprite[indexSprite]->getHeightFrame());
			//.............................................//
			
			//clamp negative position values to 0

			if(positionX<0)
			{
				positionX=0;
			}
			if(positionY<0)
			{
				positionY=0;
			}
			//display the texture with the blit function that handle the clipping
			return vectorSprite[indexSprite]->blitTextureClipped( screen, positionX, positionY,screenWidth, 
										screenHeight, frame, transparency);
		}
}

/*function that display a frame, that is to say a part of a sprite. It's displayed with a transparency 
going from 0 to 255 during duration in ms. indexSprite begins to 0.*/
bool CVisualisation::displayFrameBlending(int positionX, int positionY, int indexSprite, int /*indexFrame*/, int duration)
{
	CRectangle source(0,vectorSprite[indexSprite]->getSpriteWidth(),0,vectorSprite[indexSprite]->getSpriteHeight());
	CRectangle destination(0,this->screenWidth,0,this->screenHeight);

	//convert source rectangle into destination space

	source.setLeft(source.getLeft()+positionX);
	source.setRight(source.getRight()+positionX);
	source.setTop(source.getTop()+positionY);
	source.setBottom(source.getBottom()+positionY);

	//clip against the destination rectangle

	if(source.getLeft()<destination.getLeft())
	{
		source.setLeft(destination.getLeft());
	}

	if(source.getRight()>destination.getRight())
	{
		source.setRight(destination.getRight());
	}

	if(source.getTop()<destination.getTop())
	{
		source.setTop(destination.getTop());
	}

	if(source.getBottom()>destination.getBottom())
	{
		source.setBottom(destination.getBottom());
	}

	//convert it back into source space

	source.setLeft(source.getLeft()-positionX);
	source.setRight(source.getRight()-positionX);
	source.setTop(source.getTop()-positionY);
	source.setBottom(source.getBottom()-positionY);

	//clamp negative position values to 0

	if(positionX<0)
	{
		positionX=0;
	}
	if(positionY<0)
	{
		positionY=0;
	}
	//calcul the trensparency;
	if(this->beginTimeBlending==-1)
	{
		this->beginTimeBlending=HAPI->GetTime();
		//this->endTimeBlending=this->beginTimeBlending+duration;
	}
	float s=((float)HAPI->GetTime()-beginTimeBlending)/duration;
	int transparency=(int)(s*255);

	if(transparency>=255)
	{
		this->beginTimeBlending=-1;
		transparency=0;
		return true;
	}
	//display the texture with the blit function that handle the clipping
	
	vectorSprite[indexSprite]->blitTextureClippedBlending( screen, positionX, positionY,screenWidth, 
									screenHeight, source, transparency);
	return false;

}

/*display a sprite in the screen with a transparency going from 0 to 255 during duration. duration is in ms.*/
bool CVisualisation::displaySpriteBlending(int positionX, int positionY, int index, int duration)
{
	if(this->beginTimeBlending==-1)
	{
		this->beginTimeBlending=HAPI->GetTime();
		this->endTimeBlending=this->beginTimeBlending+duration;
	}
	float s=((float)HAPI->GetTime()-beginTimeBlending)/(endTimeBlending-beginTimeBlending);
	int transparency=(int)(s*255);
	if(transparency>=255)
	{
		this->beginTimeBlending=-1;
		transparency=0;
		return true;
	}
	this->vectorSprite[index]->blitSpriteBlending(this->screen,positionX, positionY,this->screenWidth,
		this->screenHeight,transparency);
	return false;
}

/*function that display an animation.*/
bool CVisualisation::displayAnimation(int animation, int positionX, int positionY, int* currentFrame, 
									  int* timeLastFrame, bool /*transparency*/)
{
	assert(animation>=0);
	assert(animation<(int)vectorAnimation.size());

	bool animationOver=false;
	//if it's the beginning of the animation
	if((*currentFrame)<=0 || (*currentFrame)>this->vectorAnimation[animation]->getLastFrame())
	{
		(*currentFrame)=this->vectorAnimation[animation]->getFirstFrame();
	}
	//if a certain amount of time has passed
	if((int)HAPI->GetTime()>=vectorAnimation[animation]->getOffsetTime()+(*timeLastFrame))
	{
		//if it's not the first time that the animation is played
		if((*timeLastFrame)>=0)
		{
			//if it's not the last frame
			if((*currentFrame)<this->vectorAnimation[animation]->getLastFrame())
			{
				(*currentFrame)++;
				(*timeLastFrame)=HAPI->GetTime();
			}
			else //if it's the last frame
			{
				(*timeLastFrame)=-1;
			}
		}
		else //if it's the first time that the animation is played
		{
			(*timeLastFrame)=HAPI->GetTime();
		}
	}
	//if it's the last frame
	if((*currentFrame)==this->vectorAnimation[animation]->getLastFrame() && (*timeLastFrame)==-1)
	{
		this->displayFrame(vectorAnimation[animation]->getIndexSprite(),positionX,positionY,(*currentFrame),true);
		animationOver=true;
		(*currentFrame)=-1;
	}
	else //if it's not the last frame
	{
		this->displayFrame(vectorAnimation[animation]->getIndexSprite(),positionX,positionY,(*currentFrame),true);
	}
	
	return animationOver;
}

/*function that display a health bar*/
void CVisualisation::displayHealth(int positionX, int positionY,int totalHealth, int currentHealth, int widthBar, 
								   int heightBar)
{
	if(positionX>0 && positionX+widthBar<this->screenWidth &&positionY>0 && positionY+heightBar<this->screenHeight)
	{		
		int nbPixelGreen=currentHealth*widthBar/totalHealth;
		//int nbPixelRed=widthBar-nbPixelGreen;
		HAPI_TColour red(255,0,0);
		HAPI_TColour green(0,255,0);

		for(int i=0;i<nbPixelGreen;i++)
		{
			memcpy(this->screen+(positionX*4)+(positionY*this->screenWidth*4)+4*i,&green,4);
		}
		for(int i=nbPixelGreen;i<widthBar;i++)
		{
			memcpy(this->screen+(positionX*4)+(positionY*this->screenWidth*4)+4*i,&red,4);
		}
		for(int i=1;i<heightBar;i++)
		{
			memcpy(this->screen+(positionX*4)+(positionY*this->screenWidth*4)+i*4*this->screenWidth,
				this->screen+(positionX*4)+(positionY*this->screenWidth*4),widthBar*4);
		}
		
	}
}

/*function that display the player's number of life*/
void CVisualisation::displayNumberOfLife(int positionX,int positionY, int numberOfLife, int indexSprite, int indexFrame)
{
	assert(positionX>=0 && positionX<this->screenWidth);
	assert(positionY>=0 && positionY<this->screenHeight);
	assert(numberOfLife>0);
	assert(indexSprite>=0);
	assert(indexFrame>=0);
	for(int i=0;i<numberOfLife;i++)
	{
		this->displayFrame(indexSprite,positionX+(numberOfLife-i)*40,positionY,indexFrame,true);
	}
}

/*function that clear the screen to one color*/
bool CVisualisation::clearToColor(const HAPI_TColour* color)
{
	//test if the variables get correct values
	assert(this->screenWidth>0);
	assert(this->screenHeight>0);

	int nbPixel=this->screenWidth*this->screenHeight; //number of pixel in the screen

	int time=0;
	int halfScreen=nbPixel/2;

	//I look for how many time can divided the half by 2
	while((halfScreen%2)==0)
	{
		halfScreen=halfScreen/2;
		time+=1;
	}

	// I copy one by one the color until a multiple number of the half number of pixel has the color
	for(int i=0; i<halfScreen; i++)
	{
		memcpy(this->screen+i*4,color,4);
	}
	//I copy the color in the fisrt half of the screen
	for(int i=1;i<=time;i++)
	{
		memcpy(this->screen+halfScreen*4,this->screen,halfScreen*4);
		halfScreen=halfScreen*2;
	}
	//test if the number of pixel is odd or even
	if((nbPixel%2)==0)
	{
		//I copy the second half of the screen
		memcpy(this->screen+nbPixel*2,this->screen,nbPixel*2);
	}
	else
	{
		//I copy the second half of the screen (minus one pixel)
		memcpy(this->screen+halfScreen*4,this->screen,halfScreen*4);
		//I copy the last pixel (since the number of pixel is odd)
		memcpy(this->screen+4*(nbPixel-1), this->screen,4);
	}

	return true;
}

/*function that draw one pixel to a color.*/
bool CVisualisation::setPixelToColor(const HAPI_TColour* color, int positionX, int positionY)
{
	//test if variables get correct values
	assert(this->screenWidth>=0);
	assert(this->screenHeight>=0);
	assert(positionX>=0);
	assert(positionY>=0);
	assert(positionX<=this->screenWidth);
	assert(positionY<=this->screenHeight);
	
	//calcul of the offset
	int offset = (positionX+positionY*this->screenWidth)*4;
	//set the pixel to the color
	memcpy(this->screen+offset,color,4);
	return true;
}
//gettor//
int CVisualisation::getScreenWidth(void) const
{
	return this->screenWidth;
}
int CVisualisation::getScreenHeight(void) const
{
	return this->screenHeight;
}
BYTE* CVisualisation::getScreen(void) const
{
	return this->screen;
}
int CVisualisation::getFrameWidth(int index)const
{
	return this->vectorSprite[index]->getWidthFrame();
}
int CVisualisation::getFrameHeight(int index)const
{
	return this->vectorSprite[index]->getHeightFrame();
}
int CVisualisation::getAnimationWidth(int index)const
{
	int indexSprite=this->vectorAnimation[index]->getIndexSprite();
	return this->vectorSprite[indexSprite]->getWidthFrame();
}
int CVisualisation::getAnimationHeight(int index)const
{
	int indexSprite=this->vectorAnimation[index]->getIndexSprite();
	return this->vectorSprite[indexSprite]->getHeightFrame();
}
//settor//
void CVisualisation::setScreenWidth(int screenWidth)
{
	this->screenWidth=screenWidth;
}
void CVisualisation::setScreenHeight(int screenHeight)
{
	this->screenHeight=screenHeight;
}
void CVisualisation::setScreen(BYTE* const screen)
{
	this->screen=screen;
}


/*function that display the character given in parameter. police is the index of the police 
in the vectorIndexFont vector.*/
bool CVisualisation::displayCharacter(char character, int police, int positionX, int positionY, bool transparency)
{
	const int characterSpace=32;
	if(police<0 || police>=(int)vectorIndexFont.size())
	{
		return false;
	}
	if(character!=characterSpace)
	{
		int indexChar;
		if(character>=97 && character<=122)
		{
			indexChar=character-96;
		}
		else
		{
			indexChar=character-64;
		}
		displayFrame(vectorIndexFont[police],positionX, positionY, indexChar, transparency);
	}
	return true;
}

/*function that display a digit. police is the index of the police 
in the vectorIndexFont vector.*/
bool CVisualisation::displayDigit(int digit, int police, int positionX, int positionY, bool transparency)
{
	if(police<0 || police>=(int)vectorIndexFont.size()) return false;
	int indexDigit=digit+27;
	displayFrame(vectorIndexFont[police],positionX, positionY,indexDigit, transparency);
	return true;
}

/*function tha display a text. police is the index of the police 
in the vectorIndexFont vector*/
bool CVisualisation::displayText(string text, int police, int positionX, int positionY, bool transparency)
{
	if(police<0 || police>=(int)this->vectorIndexFont.size()) return false;
	const int sizeLetter=getFrameWidth(vectorIndexFont[police]);
	for(int i=0;i<(int)text.size();i++)
	{
		displayCharacter(text[i], police, positionX+i*sizeLetter,positionY,transparency);
	}
	return true;
}

/*function that display a number. The bigger number that can be displayed is 1 million.
police is the index of the police in the vectorIndexFont vector*/
bool CVisualisation::displayNumber(int number, int police, int positionX, int positionY, bool transparency)
{
	if(police<0 || police>=(int)vectorIndexFont.size()) return false;
	const int sizeLetter=getFrameWidth(vectorIndexFont[police]);
	int result;
	int numberMax=1000000;
	bool nonZero=false;
	if(number==0)
	{
		displayDigit(0, police, positionX,positionY,transparency);
	}
	int offsetDisplay=0;
	for(int i=0;i<7;i++)
	{
		result=number/numberMax;
		if(result!=0)
		{
			nonZero=true;
		}
		if(nonZero)
		{
			displayDigit(result, police, positionX+offsetDisplay*sizeLetter,positionY,transparency);
			number-=numberMax*result;
			offsetDisplay++;
		}
		numberMax/=10;
		
	}
	return true;
}